/*
 * DisplayAspect.aj
 * 
 *  author: Daniel Rudigier
 *    date: 02.12.2008
 * 
 * 
 */

package at.momberban2.me.aspects;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.LayerManager;
import javax.microedition.lcdui.game.Sprite;

import de.enough.polish.util.ArrayList;
import de.enough.polish.util.HashMap;

import at.syncme.aspects.GameAspect;
import at.syncme.framework.Event;

import at.momberban2.me.game.M2Game;
import at.momberban2.me.gamecontext.M2Bomb;
import at.momberban2.me.gamecontext.M2Player;
import at.momberban2.me.game.M2GameState;
import at.momberban2.me.game.M2Midlet;
import at.momberban2.me.game.M2ResourceManager;
import at.momberban2.me.gamecontext.M2Map;
import at.syncme.framework.legacy.WrapperCache;
import at.syncme.framework.midp.BTServerNetworkManager;
import at.syncme.framework.midp.Media;

/**
 * display aspect for midlet profile
 * 
 * @author Daniel Rudigier
 */
public aspect DisplayAspect extends GameAspect implements CommandListener {
    private static final M2ResourceManager MGR = (M2ResourceManager) M2ResourceManager
            .getInstance();

    private M2Midlet midlet;

    private M2Game game;

    private M2GameState gs;

    private Render render;

    private List mainList, setupList, mapList, highScoreList;

    private Command setMaxPlayersCommand, selectMapCommand, startGameCommand,
            exitCommand, backCommand, againCommmand, refreshCommand,
            disconnectCommand;

    private boolean canConnect;

    pointcut midletInit(): 
        execution (M2Midlet at.momberban2.me.game.M2Midlet.ready());

    pointcut midletActive(): 
        execution (void at.momberban2.me.game.M2Midlet.startApp());

    pointcut gameStateChanged():
        call (void at.momberban2.me.game.M2Game.setGs(..));

    pointcut setupChanged(): 
        call (* at.momberban2.me.game.M2GameState.setupGame(..)) 
                || call (* at.momberban2.me.game.M2GameState.newPlayer(..))
                || call (* at.momberban2.me.game.M2GameState.playerLeft(..));

    // in-game events

    pointcut bombPlanted():
        call (at.momberban2.me.gamecontext.M2Bomb.new(..));

    pointcut bombExploded(M2Bomb bomb):
        call (* at.momberban2.me.gamecontext.M2Bomb.explode(..))
            && target(bomb);

    /**
     * initialize
     */
    after() returning (M2Midlet m): midletInit() {
        System.out.println("[display] active.");

        this.midlet = m;
        this.game = m.getGame();
        this.gs = (M2GameState) this.game.getGs();

        initCommands();
        initMainScreen();
    }

    /**
     * startup
     */
    after(): midletActive() {
        show(mainList);
    }

    /**
     * during prepare
     */
    after(): setupChanged() || gameStateChanged() {
        setupList.deleteAll();

        ArrayList players = gs.getPlayers();
        for (int i = 0; i < players.size(); i++) {
            M2Player player = (M2Player) players.get(i);
            setupList.append(player.getName(), player.getPreview());
        }
    }

    /**
     * after new services have been retrieved
     */
    after(): connectionsAvailable() {
        String[] services = this.game.getConnectionUrls();
        setupList.deleteAll();
        if (services != null) {
            for (int i = 0; (i < services.length) && (services[i] != null); i++) {
                setupList.append(services[i], null);
            }
            this.canConnect = true;
        } else {
            setupList.append(MGR.getTextFor("btNoServices"), null);
        }
    }

    /**
     * after the client is connected
     */
    after(): connectState() {
        if (this.game.isConnected()) {
            setupList.setTitle(MGR.getTextFor("connected"));
            setupList.removeCommand(refreshCommand);
            setupList.addCommand(disconnectCommand);
        } else {
            setupList.setTitle(MGR.getTextFor("disconnected"));
            setupList.addCommand(refreshCommand);
            setupList.removeCommand(disconnectCommand);
            this.canConnect = true;
        }
    }

    /**
     * after bomb.new()
     */
    after() returning (M2Bomb bomb): bombPlanted() {
        this.render.bombPlanted(bomb);
    }

    /**
     * after calling bomb.explode()
     * 
     * @param bomb
     */
    after(M2Bomb bomb) returning (Sprite[] explosionParts): bombExploded(bomb) {
        this.render.renderExplosion(bomb.getSprite(), explosionParts);
    }

    /**
     * after a game update, update the graphics
     */
    after(): gameUpdate() {
        this.render.repaint();
    }

    /**
     * initialize all commands
     */
    private void initCommands() {
        exitCommand = new Command(MGR.getTextFor("exit"), Command.EXIT, 0);
        backCommand = new Command(MGR.getTextFor("back"), Command.BACK, 0);
        againCommmand = new Command(MGR.getTextFor("nextGame"), Command.ITEM, 0);
        selectMapCommand = new Command(MGR.getTextFor("selectMap"),
                Command.ITEM, 0);
        setMaxPlayersCommand = new Command(MGR.getTextFor("setMaxPlayers"),
                Command.ITEM, 0);
        startGameCommand = new Command(MGR.getTextFor("startGame"),
                Command.ITEM, 0);
        refreshCommand = new Command(MGR.getTextFor("refresh"), Command.ITEM, 0);
        disconnectCommand = new Command(MGR.getTextFor("disconnect"),
                Command.ITEM, 0);
    }

    /**
     * init the main screen and detect everything
     */
    private void initMainScreen() {
        mainList = new List(MGR.getTextFor("gameTitle"), List.IMPLICIT);
        mainList.append(MGR.getTextFor("createGame"), null);
        mainList.append(MGR.getTextFor("joinGame"), null);
        mainList.append(MGR.getTextFor("sounds"), null);
        mainList.addCommand(exitCommand);
        mainList.setCommandListener(this);

        // first check blue-tooth (mandatory)
        if (BTServerNetworkManager.supportsBluetooth()) { // then check media
            if (Media.hasMP3Support()) {
                Media.turnSoundOn();
            } else {
                mainList.set(2, MGR.getTextFor("noMP3"), null);
                Media.turnSoundOff();
            }
        } else {
            Alert alert = new Alert(MGR.getTextFor("Error"), MGR
                    .getTextFor("noBluetooth"), null, AlertType.ERROR);
            alert.setTimeout(Alert.FOREVER);
            alert.setCommandListener(this);
        }
    }

    /**
     * init the highscore list
     */
    private void initHighScoreScreen() {
        highScoreList = new List(MGR.getTextFor("highscore"), List.IMPLICIT);
        highScoreList.addCommand(againCommmand);
        highScoreList.addCommand(exitCommand);
        highScoreList.setCommandListener(this);
    }

    private void initSetupScreen(boolean isServer) {
        if (isServer) {
            setupList = new List(MGR.getTextFor("setupServer"), List.IMPLICIT);
            setupList.addCommand(selectMapCommand);
            setupList.addCommand(setMaxPlayersCommand);
            setupList.addCommand(startGameCommand);
        } else {
            setupList = new List(MGR.getTextFor("setupClient"), List.IMPLICIT);
            setupList.append(MGR.getTextFor("btDiscover"), null);
            setupList.addCommand(refreshCommand);
        }

        setupList.addCommand(backCommand);
        setupList.setCommandListener(this);
    }

    private void initMapList() {
        mapList = new List(MGR.getTextFor("selectMap"), List.EXCLUSIVE);

        M2Map[] maps = MGR.getMaps();
        for (int i = 0; i < maps.length; i++) {
            mapList.append(maps[i].getTitle(), null);
        }

        mapList.addCommand(backCommand);
        mapList.setCommandListener(this);
    }

    /**
     * show a displayable
     * 
     * @param d
     */
    private void show(Displayable d) {
        Display.getDisplay(midlet).setCurrent(d);
    }

    private void setupEvent(short id) {
        this.game.setupEvent(Event.create(id, Event.PRIORITY_HIGH));
    }

    private void setupEvent(short id, Object payload) {
        this.game.setupEvent(Event.create(id, payload, Event.PRIORITY_HIGH));
    }

    /**
     * override
     * 
     * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command,
     *      javax.microedition.lcdui.Displayable)
     */
    public void commandAction(Command cmd, Displayable d) {
        // handle commands in main list
        if (d == mainList) {
            if (cmd == exitCommand) {
                this.midlet.terminate(true);
            } else {
                switch(mainList.getSelectedIndex()) {
                    // host new game
                    case 0:
                        initSetupScreen(true);
                        show(setupList);
                        setupEvent(Event.SETUP_SERVER);
// gameEvent(M2Event.SETUP_MAP);
                        break;
                    // join game
                    case 1:
                        initSetupScreen(false);
                        show(setupList);
                        setupEvent(Event.SETUP_CLIENT);
// gameEvent(M2Event.SETUP_MAP);
                        break;
                    case 2:
                        break;
                }
            }
        } else if (d == setupList) { // setup
            if (cmd == backCommand) { 
                show(mainList);
                setupEvent(Event.SETUP_EXIT);
            } else if (cmd == selectMapCommand) {
                initMapList();
                show(mapList);
            } else if (cmd == startGameCommand) {
                render = new Render();
                show(this.render);
                setupEvent(Event.SETUP_START);
            } else if (cmd == refreshCommand) {
                this.canConnect = false;
                setupEvent(Event.SETUP_CLIENT);
            } else if (cmd == disconnectCommand) {
                setupEvent(Event.SETUP_DISCONNECT);
            } else {
                if (this.canConnect) {
                    Integer index = WrapperCache.valueOf(setupList.getSelectedIndex());
                    setupEvent(Event.SETUP_CONNECT, index);
                }
            }
        } else if (d == mapList) { // select map
            if (cmd == backCommand) { 
                show(setupList);
            } else {
                // not reachable
            }
        }
    }

    /**
     * the in-game rendering
     */
    public final class Render extends GameCanvas {
        private final byte explosionRenderCycles = Byte.parseByte(MGR
                .getCfgFor("explosionRenderCycles"));

        private int displayWidth, displayHeight, deltaX, deltaY, bWidth,
                bHeight;

        private LayerManager world, effects, players;

        private HashMap effectsMap;

        private M2Player currentPlayer;

        private M2Map map;

        /**
         * initialize
         */
        public Render() {
            super(false);
            this.setFullScreenMode(true);
            this.displayWidth = getWidth();
            this.displayHeight = getHeight();

            this.world = new LayerManager();
            this.players = new LayerManager();
            this.effects = new LayerManager();
            this.effectsMap = new HashMap();

            ArrayList allPlayers = gs.getPlayers();
            for (int i = 0; i < allPlayers.size(); i++) {
                M2Player player = (M2Player) allPlayers.get(i);
                this.players.append(player.getSprite());
            }

            this.currentPlayer = (M2Player) allPlayers.get(0);
            this.map = gs.getMap();

            // this.lm.append(this.map.getItemLayer());
            this.world.append(this.map.getUnbreakableLayer());
            this.world.append(this.map.getBreakableLayer());
            this.world.append(this.map.getBackgroundLayer());

            this.bWidth = this.map.getBackgroundLayer().getWidth();
            this.bHeight = this.map.getBackgroundLayer().getHeight();

            ready();
        }

        /**
         * render a bomb explosion
         * 
         * @param bomb
         * @param explosionParts
         */
        public void renderExplosion(Sprite bomb, Sprite[] explosionParts) {
            for (int i = 0; i < explosionParts.length; i++) {
                this.effectsMap.put(explosionParts[i], WrapperCache.valueOf(0));
                this.effects.append(explosionParts[i]);
                this.effects.remove(bomb);
            }
        }

        /**
         * add the bomb to the layer
         * 
         * @param bomb
         */
        public void bombPlanted(M2Bomb bomb) {
            this.effects.append(bomb.getSprite());
        }

        /**
         * let there be advice
         */
        private Render ready() {
            return this;
        }

        /**
         * let there be advice
         */
        private int actionCode(int keyCode) {
            return getGameAction(keyCode);
        }

        protected void keyPressed(int keyCode) {
            actionCode(keyCode);
            // get advised
        };

        protected void keyReleased(int keyCode) {
            // get advised
        };

        /**
         * repaint
         */
        public void paint(Graphics g) {
            // scrolling part one
            Sprite s = currentPlayer.getSprite();
            deltaX = origin(s.getX() + s.getWidth() / 2, bWidth, displayWidth);
            deltaY = origin(s.getY() + s.getHeight() / 2, bHeight,
                    displayHeight);
            g.setClip(deltaX, deltaY, bWidth, bHeight);
            g.translate(deltaX, deltaY);

            // cleanup effects
            for (int i = 0; i < this.effectsMap.size(); i++) {
                s = (Sprite) this.effectsMap.keys()[i];
                int cycles = ((Integer) this.effectsMap.get(s)).intValue();
                if (cycles > explosionRenderCycles) {
                    this.effects.remove(s);
                    this.effectsMap.remove(s);
                } else {
                    this.effectsMap.put(s, WrapperCache.valueOf(cycles + 1));
                }
            }

            // x,y = relative positions of window
            this.world.paint(g, 0, 0);
            this.players.paint(g, 0, 0);
            this.effects.paint(g, 0, 0);

            // scrolling part two
            g.translate(-deltaX, -deltaY);
            g.setClip(0, 0, displayWidth, displayHeight);
        }

        /**
         * scrolling helper method
         */
        private int origin(final int focus, final int fieldLength,
                final int screenLength) {
            int origin;

            if (screenLength >= fieldLength) {
                origin = (screenLength - fieldLength) / 2;
            } else if (focus <= screenLength / 2) {
                origin = 0;
            } else if (focus >= (fieldLength - screenLength / 2)) {
                origin = screenLength - fieldLength;
            } else {
                origin = screenLength / 2 - focus;
            }

            return origin;
        }
    }
}
